﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NEH_SA.Model;
using NEH_SA.Utilities;

namespace NEH_SA.ACO
{
    public class Paco
    {
        public static Rozwiązanie WygenerujRozwiązanie(Rozwiązanie nehResult, Problem problem)
        {
            const double ro = 0.75;
            var rand = new Random();

            var permutationBest = Tools.GenerujPoczątkowąPermutacje(problem.LiczbaZadań, rand);
            var cMaxBest = permutationBest.ObliczCMax(problem);

            //var permutationBest = nehResult.Permutacja.ToList();
            //var cMaxBest = nehResult.CMax;
            //Initialize pheromon values
            var pheromons = InitializePheromons(problem.LiczbaZadań, permutationBest, cMaxBest);

            // 50 ant sequences

            for (int iteracje = 0; iteracje < 50; iteracje++)
            {

                var sLocal = new List<int>();
                var unScheduled = permutationBest.ToList();

                var accumulatedPheromon = GetAccumulatedPheromon(problem.LiczbaZadań, pheromons);

                // construct trail
                for (int position = 0; position < problem.LiczbaZadań; position++)
                {
                    var u = rand.NextDouble();
                    //Take first unscheduled job
                    if (u <= 0.4)
                    {
                        sLocal.Add(unScheduled.First());
                        unScheduled.RemoveAt(0);
                    }
                    // Take job withe the highest pheromon accumulation value from the first 5 jobs
                    else if (u <= 0.8)
                    {
                        var job = unScheduled.Take(5).OrderBy(i => accumulatedPheromon[i, position]).First();
                        sLocal.Add(job);
                        unScheduled.Remove(job);
                    }
                    // Roulette 
                    else
                    {
                        var jobs = unScheduled.Take(5).ToList();
                        var sum = jobs.Sum(i => accumulatedPheromon[i, position]);
                        var probValues = jobs.Select(i => accumulatedPheromon[i, position] / sum).ToList();
                        for (int i = 1; i < probValues.Count; i++)
                        {
                            probValues[i] += probValues[i - 1];
                        }
                        int job = int.MinValue;
                        var u2 = rand.NextDouble();
                        for (int i = 0; i < probValues.Count; i++)
                        {
                            if (u2 <= probValues[i])
                            {
                                job = jobs[i];
                                break;
                            }
                        }
                        sLocal.Add(job);
                        unScheduled.Remove(job);
                    }

                }
                // local search
                var localCmax = sLocal.ObliczCMax(problem);

                var localSearchBestCmax = localCmax;
                var localSearchBestPermutation = sLocal.ToList();


                for (int i = 0; i < problem.LiczbaZadań; i++)
                {
                    for (int k = 0; k < problem.LiczbaZadań; k++)
                    {
                        if (i != k)
                        {

                            var tmpList = sLocal.Move(i, k);
                            var tmpCMax = tmpList.ObliczCMax(problem);
                            if (tmpCMax < localSearchBestCmax)
                            {
                                localSearchBestCmax = tmpCMax;
                                localSearchBestPermutation = tmpList;
                            }
                            if (i < k)
                            {
                                tmpList = sLocal.Swap(i, k);
                                tmpCMax = tmpList.ObliczCMax(problem);
                                if (tmpCMax < localSearchBestCmax)
                                {
                                    localSearchBestCmax = tmpCMax;
                                    localSearchBestPermutation = tmpList;
                                }
                            }

                        }
                    }
                }

                // update pheromon trails
                var liczbaZadań = problem.LiczbaZadań;


                int upperBound = 1;
                if (liczbaZadań > 40) upperBound++;

                for (int i = 0; i < liczbaZadań; i++)
                {
                    for (int k = 0; k < liczbaZadań; k++)
                    {

                        if (Math.Abs(localSearchBestPermutation.IndexOf(i) - k) <= upperBound)
                        {
                            pheromons[i, k] = ro * pheromons[i, k] +
                                (1.0 / (localSearchBestCmax * Math.Sqrt(Math.Abs(permutationBest.IndexOf(i) - k) + 1)));
                        }
                        else
                        {
                            pheromons[i, k] = ro * pheromons[i, k];
                        }

                    }
                }


                // if better then best
                if (localSearchBestCmax < cMaxBest)
                {
                    cMaxBest = localSearchBestCmax;
                    permutationBest = localSearchBestPermutation;
                }

            }

            return new Rozwiązanie(permutationBest, cMaxBest, problem.Nazwa, Algorytm.Aco);

        }

        /// <summary>
        /// Gets the accumulated pheromon values
        /// </summary>
        /// <param name="liczbaZadań"></param>
        /// <param name="pheromons"></param>
        /// <returns></returns>
        private static double[,] GetAccumulatedPheromon(int liczbaZadań, double[,] pheromons)
        {
            var accumulatedPheromon = new double[liczbaZadań, liczbaZadań];

            for (int i = 0; i < liczbaZadań; i++)
            {
                accumulatedPheromon[i, 0] = pheromons[i, 0];
            }
            for (int i = 0; i < liczbaZadań; i++)
            {
                for (int j = 1; j < liczbaZadań; j++)
                {
                    accumulatedPheromon[i, j] = accumulatedPheromon[i, j - 1] + pheromons[i, j];
                }
            }
            return accumulatedPheromon;
        }

        /// <summary>
        /// Initialize Pheromons
        /// </summary>
        /// <param name="liczbaZadań"></param>
        /// <param name="bestPermutation"></param>
        /// <param name="zBest"></param>
        /// <returns></returns>
        private static double[,] InitializePheromons(int liczbaZadań, List<int> bestPermutation, int zBest)
        {
            var pheromons = new double[liczbaZadań, liczbaZadań];
            for (int i = 0; i < liczbaZadań; i++)
            {
                for (int k = 0; k < liczbaZadań; k++)
                {
                    double newValue;
                    if ((Math.Abs(bestPermutation.IndexOf(i) - k)) + 1 <= liczbaZadań / 4)
                    {
                        newValue = 1.0 / zBest;
                    }
                    else if ((Math.Abs(bestPermutation.IndexOf(i) - k)) + 1 <= liczbaZadań / 2)
                    {
                        newValue = 1.0 / (2 * zBest);
                    }
                    else
                    {
                        newValue = 1.0 / (4 * zBest);
                    }

                    pheromons[i, k] = newValue;
                }
            }
            //for (int i = 0; i < liczbaZadań; i++)
            //{
            //    for (int k = 0; k < liczbaZadań; k++)
            //    {
            //        pheromons[i, k] = 1.0 / liczbaZadań;
            //    }
            //}
            return pheromons;
        }
    }
}
